package contable.aplicacion.sql;

import contable.aplicacion.Notificaciones;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.swing.JOptionPane;

/**
 *
 * @author suncin
 */
public class MySQL {

    /**
     * Almacena la dirección de el servidor donde esta la base de datos de
     * interés
     */
    private String servidor;
    /**
     * Almacena el puerto por el cual se conectará al servidor
     */
    private String puerto;
    /**
     * Almacena el nombre de la base de datos de interés
     */
    private String baseDeDatos;
    /**
     * Almacena el nombre del usuario que se conectara al servidor y a la base
     */
    private String usuario;
    /**
     * Almacena la contraseña del usuario
     */
    private transient String contrasena;
    /**
     * Almacena la conexión entre la gestor de la base de datos y el sistema
     */
    private Connection conexion;
    /**
     * Almacena la consulta junto a los parámetros
     */
    private PreparedStatement sentencia;
    /**
     * Almacena el controlador JDBC
     */
    private Driver controlador;
    /**
     * Referencia a la instancia única de la clase
     */
    private static final MySQL INSTANCIA = new MySQL();

    /**
     * Constructor por defecto de la clase, que carga la configuraciones
     */
    private MySQL() {
        try {
            controlador = new com.mysql.jdbc.Driver();
            DriverManager.registerDriver(controlador);
        } catch (SQLException ex) {
            Notificaciones.notificarError(MySQL.class, ex.getClass().getName(), ex);
        }
        conexion = null;
        sentencia = null;
    }

    /**
     * Obtener la instancia de la clase
     *
     * @return La instancia única de la clase
     */
    public static MySQL getInstancia() {
        return INSTANCIA;
    }

    /**
     * Establecer los valores de la instancia
     *
     * @param servidor la dirección del servidor
     * @param puerto el numero de puerto que se usara para la conexión
     * @param usuario el usuario de la BD
     * @param contrasenha la contraseña del usuario
     * @param baseDeDatos el nombre de la base de datos
     */
    public void setInstancia(String servidor, String puerto, String usuario, String contrasenha, String baseDeDatos) {
        setServidor(servidor);
        setPuerto(puerto);
        setUsuario(usuario);
        setContrasenha(contrasenha);
        setBaseDeDatos(baseDeDatos);
    }

    /**
     * Método para ingresar uno o varios registro en la base de datos
     *
     * @param consulta contiene la consulta de inserción a realizar
     * @return si no ocurrió ningún error en la consulta retorna 0<br> si ocurre
     * un error retorna el código del error
     */
    public int insertarRegistro(String consulta, Object... objetos) {
        return ejecutarModificacion(consulta, objetos);
    }

    /**
     * Método para actualizar uno o varios registro en la base de datos
     *
     * @param consulta contiene la consulta de actualización a realizar
     * @return si no ocurrió ningún error en la consulta retorna 0<br> si ocurre
     * un error retorna el código del error
     */
    public int actualizarRegistro(String consulta, Object... objetos) {
        return ejecutarModificacion(consulta, objetos);
    }

    /**
     * Método para eliminar uno o varios registro en la base de datos
     *
     * @param consulta contiene la consulta de eliminación a realizar
     * @return si no ocurrió ningún error en la consulta retorna 0<br> si ocurre
     * un error retorna el código del error
     */
    public int eliminarRegistro(String consulta, Object... objetos) {
        return ejecutarModificacion(consulta, objetos);
    }

    /**
     * Método para consultar uno o varios registro en la base de datos
     *
     * @param consulta contiene la solicitud de consulta a realizar
     * @return un objeto ResultSet con el resultado de la consulta
     * @see java.sql.ResultSet
     */
    public ResultSet consultarRegistro(String consulta,
            Object... objetos) {
        ResultSet resultado = null;
        try {
            conexion = DriverManager.getConnection(cadenaConexion(), usuario,
                    contrasena);
            sentencia = conexion.prepareStatement(consulta);
            for (int i = 0; i < objetos.length; i++) {
                sentencia.setObject(i + 1, objetos[i]);
            }
            resultado = sentencia.executeQuery();
        } catch (SQLException ex) {
            Notificaciones.notificarError(MySQL.class, ex.getClass().getName(), ex);
        }
        return resultado;
    }

    /**
     * Método para consultar si existen uno o varios registro en la base de
     * datos
     *
     * @param consulta contiene la consulta existencia a realizar
     * @return el numero de registros existentes que coinciden en la base datos,
     * y -1 en caso de un error en la consulta
     */
    public int contarRegistros(String consulta, Object... objetos) {
        int retorno = 0;
        ResultSet rs;
        rs = consultarRegistro(consulta, objetos);
        try {
            if (rs.next() && rs != null) {
                retorno = rs.getInt(1);
            }
        } catch (java.sql.SQLException ex) {
            Notificaciones.notificarError(MySQL.class, ex.getClass().getName(), ex);
            retorno = -1;
        }
        return retorno;
    }

    /**
     * Método para consultar si existe una base de datos en el servidor
     *
     * @return Si ocurre un error en la consulta -1<br>Si ningún error ocurre
     * retorna el numero de bases de datos que concuerdan
     */
    public int existeBaseDeDatos(String baseDeDatos) {
        int retorno = 0;
        String consulta = "SELECT COUNT(*) FROM SCHEMATA WHERE SCHEMA_NAME = ?";
        ResultSet rs;
        try {
            conexion = DriverManager.getConnection("jdbc:mysql://" + servidor + ":" + puerto
                    + "/information_schema", usuario, contrasena);
            sentencia = conexion.prepareStatement(consulta);
            sentencia.setString(1, baseDeDatos);
            rs = sentencia.executeQuery();
            if (rs.next()) {
                retorno = rs.getInt(1);
            }
        } catch (SQLException ex) {
            Notificaciones.notificarError(MySQL.class, ex.getClass().getName(), ex);
            retorno = -1;
        }
        return retorno;
    }

    /**
     * Método para consultar si existe una tabla en la base de datos
     *
     * @param tabla el nombre de la tabla a buscar
     * @return Si ocurre un error en la consulta -1<br>Si ningún error ocurre
     * retorna el numero de tablas que concuerdan
     */
    public int existeTabla(String tabla) {
        int retorno = 0;
        String consulta = "SELECT COUNT(*) FROM TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?";
        ResultSet rs;
        try {
            conexion = DriverManager.getConnection("jdbc:mysql://" + servidor + ":" + puerto
                    + "/information_schema", usuario, contrasena);
            sentencia = conexion.prepareStatement(consulta);
            sentencia.setString(1, baseDeDatos);
            sentencia.setString(2, tabla);
            rs = sentencia.executeQuery();
            if (rs.next()) {
                retorno = rs.getInt(1);
            }
        } catch (SQLException ex) {
            Notificaciones.notificarError(MySQL.class, ex.getClass().getName(), ex);
            retorno = -1;
        }
        return retorno;
    }

    /**
     * Método para crear la base una base de datos en caso de que no exista
     *
     * @return si no ocurrió ningún error en la consulta retorna 0<br> si ocurre
     * un error retorna el código del error
     */
    public int crearBaseDeDatos(String baseDeDatos) {
        int retorno = 0;
        String consulta = "CREATE DATABASE " + baseDeDatos
                + " DEFAULT CHARACTER SET utf8 COLLATE utf8_spanish2_ci";
        try {
            conexion = DriverManager.getConnection("jdbc:mysql://" + servidor + ":" + puerto
                    + "/information_schema", usuario, contrasena);
            sentencia = conexion.prepareStatement(consulta);
            sentencia.execute();
        } catch (SQLException ex) {
            Notificaciones.notificarError(MySQL.class, ex.getClass().getName(), ex);
            retorno = ex.getErrorCode();
        }
        return retorno;
    }

    /**
     * Método para crear una tabla en la base de datos en caso de que no exista
     *
     * @param consulta contiene la consulta de definición de la tabla
     * @return si no ocurrió ningún error en la consulta retorna 0<br> si ocurre
     * un error retorna el código del error
     */
    public int crearTabla(String consulta) {
        return ejecutarModificacion(consulta);
    }

    /**
     * Método para ejecutar una consulta de modificación de datos
     *
     * @param consulta contiene la consulta de modificación
     * @return si no ocurrió ningún error en la consulta retorna 0<br> si ocurre
     * un error retorna el código del error
     */
    private int ejecutarModificacion(String consulta,
            Object... objetos) {
        int retorno = 0;
        try {
            conexion = DriverManager.getConnection(cadenaConexion(), usuario,
                    contrasena);
            sentencia = conexion.prepareStatement(consulta);
            for (int i = 0; i < objetos.length; i++) {
                sentencia.setObject(i + 1, objetos[i]);
            }
            sentencia.executeUpdate();
        } catch (SQLException ex) {
            Notificaciones.notificarError(MySQL.class, ex.getClass().getName(), ex);
            retorno = ex.getErrorCode();
        } finally {
            try {
                if (sentencia != null) {
                    sentencia.close();
                }
                if (conexion != null) {
                    conexion.close();
                }
            } catch (SQLException ex) {
                Notificaciones.notificarError(MySQL.class, ex.getClass().getName(), ex);
            }
        }
        return retorno;
    }

    /**
     * Método para definir el servidor
     *
     * @param servidor la dirección del servidor
     * @throws IllegalArgumentException cuando sea ingresado una dirección del
     * servidor invalido
     */
    public void setServidor(String servidor) {
        if (servidor == null || servidor.isEmpty()) {
            throw new IllegalArgumentException("ADVERTENCIA: Digite la direccion del servidor");
        } else {
            this.servidor = servidor;
        }
    }

    /**
     * Método para definir el puerto de conexión
     *
     * @param puerto el numero de puerto que se usara para la conexión
     * @throws IllegalArgumentException cuando no se ingresa un numero entero
     */
    public void setPuerto(String puerto) {
        if (puerto == null || puerto.isEmpty()) {
            throw new IllegalArgumentException("ADVERTENCIA: Digite el numero de puerto");
        }
        if (puerto.matches("\\d+")) {
            this.puerto = puerto;
        } else {
            throw new IllegalArgumentException("ERROR: el puerto debe ser un numero entero");
        }
    }

    /**
     * Método para definir el nombre de la base de datos
     *
     * @param baseDeDatos el nombre de la base de datos
     * @throws IllegalArgumentException cuando el nombre de la base de datos sea
     * invalido
     */
    public void setBaseDeDatos(String baseDeDatos) {
        int r1, r2;
        if (baseDeDatos == null || baseDeDatos.isEmpty()) {
            throw new IllegalArgumentException("ADVERTENCIA: Escriba el nombre de la base de datos");
        } else if (existeBaseDeDatos(baseDeDatos) == 1) {
            this.baseDeDatos = baseDeDatos;
        } else if (existeBaseDeDatos(baseDeDatos) == 0) {
            r1 = JOptionPane.showConfirmDialog(
                    null,
                    "La base de datos no existe en el servidor\n¿Desea crearla?",
                    "Crear nueva base de datos",
                    JOptionPane.YES_NO_OPTION,
                    JOptionPane.QUESTION_MESSAGE);
            if (r1 == JOptionPane.YES_OPTION) {
                r2 = crearBaseDeDatos(baseDeDatos);
                if (r2 == 1) {
                    this.baseDeDatos = baseDeDatos;
//                    crearTablas();
                    JOptionPane.showMessageDialog(null,
                            "Se creo la base de datos \"" + baseDeDatos
                            + "\" exitosamente", "Existo",
                            JOptionPane.INFORMATION_MESSAGE);
                } else {
                    throw new IllegalArgumentException("ADVERTENCIA: No se pudo crear la base de datos");
                }
            } else {
                throw new IllegalArgumentException("ADVERTENCIA: La base de datos no existe");
            }
        } else {
            throw new IllegalArgumentException("ERROR DE CONEXION: Revice datos de la configuracion");
        }
    }

    /**
     * Método para definir el usuario de la Base de Datos
     *
     * @param usuario el usuario de la BD
     * @throws IllegalArgumentException cuando sea ingresada un nombre de
     * usuario invalido
     */
    public void setUsuario(String usuario) {
        if (usuario == null || usuario.isEmpty()) {
            throw new IllegalArgumentException("ADVERTENCIA: Digite el nombre del usuario");
        } else {
            this.usuario = usuario;
        }
    }

    /**
     * Método para definir la contraseña del usuario
     *
     * @param contrasena la contraseña del usuario
     */
    public void setContrasenha(String contrasena) {
        this.contrasena = contrasena == null ? "" : contrasena;
    }

    /**
     * Método para obtener la cadena de conexión del JDBC
     *
     * @return la cadena de conexión de JDBC
     */
    private String cadenaConexion() {
        return "jdbc:mysql://" + servidor + ':' + puerto + '/'
                + baseDeDatos;
    }

    @Override
    public String toString() {
        return "MySQL {\n\tcontrolador = "
                + com.mysql.jdbc.Driver.class.getName() + " Version: "
                + controlador.getMajorVersion() + " Release: "
                + controlador.getMinorVersion() + "\n\tconexion = "
                + cadenaConexion() + "\n}";
    }

    @Override
    protected void finalize() throws Throwable {
        if (sentencia != null) {
            sentencia.close();
        }
        if (conexion != null) {
            conexion.close();
        }
        if (controlador != null) {
            DriverManager.deregisterDriver(controlador);
        }
        super.finalize();
    }
}
